package utils.random;

import java.util.LinkedHashMap;
import java.util.Random;
import java.util.Map;


/**
 * Generates a better even distribution of Pseudo-random numbers. This is 
 * used when generating random behaviors and is implemented to avoid the repetition
 * of calls to Math.Random() in a short period of time.
 * 
 * @author Guillermo Solano
 * @version 1.0
 * @date February '11
 * 
 */

public class RandomGeneration
{
	/* Number of bits to use in the first seed generation*/
	private final static int BITS_INTIAL_SEED_GENERATION = 32;
	
	/* 
	 * Minimal expected difference between the previous double pseudo-random number
	 * that was generated. When this difference is not commited, it will continue
	 * generating randoms numbers until the expected difference, at least, has 
	 * been reached.
	 */
	//private double MINIMAL_EXPECTED_DIFFERENCE;
	//now unique for each version - see inner class RandomData
	
	/*
	 * Keeps a Map for each version of the random generator we want to use. By 
	 * giving a different name or version, it will keep different generator. For
	 * example this is useful to use a different version in each important method
	 * generator.
	 */
	private Map <String, RandomData> randomMap;
	
	/* 
	 * Stores the last pseudo-random number that was generated for a concrete version.
	 */
	//private Map <String, Double> lastRandomDouble;
	//now unique for each version - see inner class RandomData
	
	/* 
	 * The MersenneTwister Algorithm is used to generate the initial seed. This generator 
	 * algorithm is one of the best known, but is more intended for encrypt PRNG due to
	 * that is much more slower than the java.util.Random class. For this reason, the
	 * MersenneTwister algorithm is used only once, to generate a good seed for the 
	 * java.util.Random; which is the one used in the next PRNG generations.
	 * 
	 */
	private MTRandom seed;
	
	
	/**
	 * Basis constructor
	 */
	public RandomGeneration()
	{
		this.randomMap = new LinkedHashMap<String, RandomData>();
		this.seed = new MTRandom(BITS_INTIAL_SEED_GENERATION);	
	}
	
	/**
	 * @param version of the set of PRNG double numbers generated
	 * @return next double PRNG that satisfies the  minimal expected
	 * 		difference set by the changeMinimalDifference method.
	 */
	public synchronized double giveMeNextDoubleRandom(String version)
	{
		double generated = 0.0;
		RandomData rd = new RandomData();
		rd.previous = 0.2;
		
		if (!this.randomMap.containsKey(version)) //it hasn't been initialized this version
			insertNewRandomData(version);
				
		generated = this.randomMap.get(version).random.nextDouble();
		
		//check if it satifies the minimum difference with the previous one
		while (Math.abs(generated - this.randomMap.get(version).previous) <= this.randomMap.get(version).difference)
			generated = this.randomMap.get(version).random.nextDouble();
		
		this.randomMap.get(version).previous = generated;
		
		return generated;
	}
	
	/**
	 * @param difference minimal difference between two consecutive PNRG 
	 * 		generations.
	 */
	public synchronized void changeMinimalDifference (Double difference, String version)
	{
		if (this.randomMap.containsKey(version))
			this.randomMap.get(version).difference = difference;
		else
			insertNewRandomData(version);
				
	}
	
	/*
	 * private method that inserts a new RandomData with the specified version
	 */
	private synchronized void insertNewRandomData(String version)
	{
		RandomData rd = new RandomData();
		rd.difference = 0.0;
		rd.previous = 0.0;
		rd.random = new Random(this.seed.nextInt());			
		this.randomMap.put(version, rd);
	}
	
	/* inner class inside RandomGeneration to keep track of t previous random generated,the
	 * minimal expected difference and the current Random (all three for each version)
	 */
	private class RandomData
	{
		public Double previous;
		
		public Double difference;
		
		public Random random;
		
		public RandomData(){ 
			previous = difference = 0.0;
			random = null;
			}
		
	} //end inner class
	
	//small tests
	/*
	public static void main (String pepe[])
	{
		RandomGeneration rg = new RandomGeneration();
		
		for (int i= 0; i<100; i++)
		{
			System.out.println( rg.giveMeNextDoubleRandom("pepe1"));
		}
		
		System.out.println();
		rg.changeMinimalDifference(0.5, "pepe2");
		
		for (int i= 0; i<100; i++)
		{
			System.out.println( rg.giveMeNextDoubleRandom("pepe2"));
		}
		
		System.out.println();
		rg.changeMinimalDifference(0.05, "pepe3");
		
		for (int i= 0; i<100; i++)
		{
			System.out.println( rg.giveMeNextDoubleRandom("pepe3"));
		}	
	}
	*/
	
	

}
